using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Xml.Schema;
using System.Collections;
using NLog;
using System.Windows.Forms;
using System.Diagnostics;
using System.Data;
using System.IO;

namespace MyClever.Lib
{
    /// <summary>
    /// Whenever you working with data or files on your machine, you have an intention what you like to achieve with them.
    /// While there are so many possible customer expectations, we can give you a few examples only:
    /// <list type="bullet">
    /// <item>
    /// <description>finding ineffective cost factors of your business</description>
    /// </item>
    /// <item>
    /// <description>listening to music corresponding to your current emotions</description>
    /// </item>
    /// <item>
    /// <description>updating your friends/partners/family of your life/business</description>
    /// </item>
    /// <item>
    /// <description>getting access to relevant informations for your next holiday destination</description>
    /// </item>
    /// <item>
    /// <description>...</description>
    /// </item>
    /// </list>
    /// Regardless of your application area (business, family, etc.) you will always have some <see cref="SourcePlugin">resources</see> 
    /// that you need to <see cref="ActionPlugin">interact</see> with, satisfying your dreams and wishes.
    /// In the philosophy of MyCleverFriend a <see cref="Mission"/> is the working plan to just achieve them.
    /// </summary>
    public class Mission
    {
        /// <summary>
        /// Contains the content of the mission-xml-file, we need for creating a
        /// workable instance of a mission.
        /// </summary>
        private XmlDocument missionDocument;
        
        /// <summary>
        /// Contains the misson specifications like author, version, description ... of this mission.
        /// You can set this informations in your mission-XML-file by using "Mission/Specification" 
        /// </summary>
        Dictionary<string, string> specification;

        /// <summary>
        /// Contains all plugins, which were defined in the loaded mission-xml-file, grouped
        /// in a new collection for every given new plugin-loop. 
        /// </summary>
        MissionPluginLoopNode pluginCollection;

        private static Logger logger = LogManager.GetCurrentClassLogger();

        public event System.ComponentModel.ProgressChangedEventHandler ProgressChanged;        
        // Invoke the Changed event; called whenever list changes
        protected virtual void OnProgressChanged(System.ComponentModel.ProgressChangedEventArgs e)
        {
            if (null != ProgressChanged)
            {
                ProgressChanged(this, e);    
            }
        }
        #region Properties
        /// <summary>
        /// Gets the name of this mission.
        /// </summary>
        /// <value>The name of this mission. Whenever you (or your customers) search accurately this mission,
        /// they can ask for this name.</value>
        public string Name
        {
            get 
            {
                if (this.specification.ContainsKey("name"))
                {
                    return this.specification["name"];
                }
                // if the mission was loaded not by a xml-file
                else
                {
                    return "untitled";
                }
            }
        }

        /// <summary>
        /// Gets the description of this mission.
        /// </summary>
        /// <value>The description of this mission. You can use one ore more sentence to explain the 
        /// motivation to compute this mission.</value>
        public string Description
        {
            get { return this.specification["description"]; }
        }

        /// <summary>
        /// Gets the author of this mission.
        /// </summary>
        /// <value>The author  of this mission. You can set your name or company-name here.</value>
        public string Author
        {
            get { return this.specification["author"]; }
        }

        /// <summary>
        /// Gets the version of this mission.
        /// </summary>
        /// <value>The version of this mission using the following syntax: Major.Minor.Build</value>
        public string Version
        {
            get { return this.specification["version"]; }
        }

        /// <summary>
        /// Gets some keywords which describes this mission.
        /// </summary>
        /// <value>Comma seperated keywords which describe this mission. 
        /// By using other words as in the <see cref="Description"/> you can help other users to find this plugin on the "MyCleverFriend"-website.</value>
        public string Tags
        {
            get { return this.specification["tags"]; }
        }

        public Dictionary<string, string> Specification
        {
            get { return this.specification; } 
        }

        public MissionPluginLoopNode PluginCollection
        {
            get { return this.pluginCollection; }
        }
#endregion

        public Mission()
        {
            this.specification = new Dictionary<string, string>();

            this.pluginCollection = new MissionPluginLoopNode(0, null);
        }

        /// <summary>
        /// Creates a new instance of the <see cref="Mission"/> class, depending on the given mission.xml-file blueprint.
        /// </summary>
        /// <param name="xmlPath">The the mission.XML-file-path which defines the blueprint of the new instance.</param>
        public Mission(string xmlPath) : this()
        {            
            logger.Info("Load new mission from {0}", xmlPath);
            this.missionDocument = new XmlDocument();
            this.missionDocument.Load(xmlPath);
#if !MONO
            this.missionDocument.Schemas.Add("", Application.StartupPath + "\\Missions\\Mission.xsd");
            this.missionDocument.Validate(new ValidationEventHandler(missionSettingsValidationEventHandler));
#endif

            // save mission specifications
            this.specification = MissionXMLReader.GetSpecifications(ref missionDocument);

            // save all mission plugins
            this.pluginCollection = MissionXMLReader.GetPluginCollection(ref missionDocument);
        }

        /// <summary>
        /// Gets a valid plugin-instance depending on the pluginID. 
        /// </summary>
        /// <param name="pluginID">The plugin ID which you can define in the mission.xml-file.</param>
        /// <returns>Returns a valid instance of the plugin.</returns>
        public MissionPluginProxy GetPluginByID(int pluginID)
        {
            return this.pluginCollection.GetPluginByID(pluginID);
        }
        
        /// <summary>
        /// Returns a list of plugins, which have execute at least one error message.
        /// </summary>
        /// <returns></returns>
        public List<MissionPluginProxy> GetDefectivePlugins()
        {
            return this.pluginCollection.GetDefectivePlugins();
        }   
       
        /// <summary>
        /// By calling this method, you will start the complete process of the data-workflow
        /// which was loaded from the requested mission.xml-file.
        /// While every expected plugin must be loaded and activated this process can be very 
        /// time-consuming.
        /// </summary>
        public void Start()
        {
            logger.Info("Trying to start mission.");

            // then start all plugins in working order
            int pluginStarted = 0;

            MissionPluginProxy pluginOn = null;
            while (null != (pluginOn = this.pluginCollection.MoveNext()))
            {
                logger.Info("starte plugin: {0} from: {1}", pluginOn.ClassName, pluginOn.Path.FullPath);
                pluginStarted++;

                pluginOn.Start();

                int progressInPercent = 0; //crashs!! use numPluginsToStart (with all loops counts) instead! // 100 / this.NumAppliedPlugins * pluginStarted;
                OnProgressChanged(new System.ComponentModel.ProgressChangedEventArgs(progressInPercent, true));

                // has the first called package (=main-package) reached 100 percent?
                // todo: i don't like the way using the select-Method. isn't there a better way?
                if (true == this.GetDefectivePlugins().Count > 0 || pluginOn.GetPackageByName("Logging").Rows.Count == 0 || 1 != pluginOn.GetPackageByName("Logging").Select(String.Format("SettingPackageName = '{0}' And Percent = 100", pluginOn.GetPackageByName("Logging").Rows[0]["SettingPackageName"].ToString())).Length)
                {
                    logger.Fatal("Plugin '{0}' execute error!", pluginOn.ClassName);
                }
            }
            logger.Info("mission complete!");
        }

        void missionSettingsValidationEventHandler(object sender, ValidationEventArgs e)
        {
            logger.Fatal("{0}: {1}", e.Severity.ToString(), e.Message);
        }
    }
}
